package com.pvdnc.nsocket.protocol;

import com.pvdnc.nsocket.log.SLog;
import com.pvdnc.nsocket.protocol.base.Packet;
import com.pvdnc.nsocket.serialize.IEncoding;
import com.pvdnc.nsocket.serialize.UTF8Encoding;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicReference;

import io.netty.buffer.ByteBuf;

public class PacketCodec {
    private static final SLog LOG=new SLog(PacketCodec.class);

    private static final AtomicReference<PacketCodec> sInstance=new AtomicReference<>();

    public static PacketCodec getInstance(){
        if(sInstance.get()==null)
            sInstance.set(new PacketCodec());
        return sInstance.get();
    }

    private PacketCodec(){

    }

    public static final IEncoding ENCODING=new UTF8Encoding();

    private final Map<String, Interpreter> mInterpreterMap =new ConcurrentHashMap<>();

    public final <T extends Interpreter> void addInterpreter(T interpreter){
        String command=interpreter.getCommand();
        if(mInterpreterMap.containsKey(command)){
            LOG.e("interpreter of command:"+command+" cannot be overwritten");
            return;
        }
        mInterpreterMap.put(interpreter.getCommand(),interpreter);
    }

    @SuppressWarnings("unchecked")
    private <T> Interpreter<T> getInterpreter(String command){
        Interpreter interpreter=mInterpreterMap.get(command);
        if(interpreter==null)
            return null;
        if(interpreter.getCommand().equals(command))//确保强制转换可行
            return (Interpreter<T>)interpreter;
        return null;
    }

    public static final int FIXED_DATA_OFFSET=8/*offset字段的实际偏移*/;
    private int getOffset(byte[] cmdData,byte[] data){
        return cmdData.length+data.length
        +4/*sizeof(cmdData.length)*/+4/*sizeof(data.length)*/;
    }

    public <T extends Packet> void encode(T packet, ByteBuf out) throws IllegalArgumentException{
        out.writeInt(Packet.MAGIC);
        out.writeInt(packet.mVersion);
        //查找命令解释器
        String command=packet.getCommand();
        Interpreter<T> interpreter=getInterpreter(command);
        if(interpreter==null)
            throw new IllegalArgumentException("there is no interpreter for command:"+command);
        //包装数据
        byte[] cmdData= ENCODING.getBytes(command);//11 for demo
        byte[] data=interpreter.toData(packet);//41 for demo
        //写入长度
        int offset=getOffset(cmdData,data);
        out.writeInt(offset);//写入剩余长度(60 for demo)
        //写入数据
        out.writeInt(cmdData.length);
        out.writeBytes(cmdData);
        out.writeInt(data.length);
        out.writeBytes(data);
    }

    public <T extends Packet> T decode(ByteBuf in) throws IllegalArgumentException{
        int magic=in.readInt();
        int version=in.readInt();
        //跳过OFFSET区段的长度
        int offset= in.readInt();
        //读取命令
        int cmdLength=in.readInt();
        checkLength(in,cmdLength);
        byte[] cmdData=new byte[cmdLength];
        in.readBytes(cmdData);
        String command=ENCODING.getString(cmdData);
        //查找命令解释器
        Interpreter<T> interpreter=getInterpreter(command);
        if(interpreter==null)
            throw new IllegalArgumentException("there is no interpreter of command:"+command);;
        //读取数据
        int dataLength=in.readInt();
        checkLength(in,dataLength);
        //确保包合法
        byte[] data=new byte[dataLength];
        int actualOffset=getOffset(cmdData,data);
        if(offset!=actualOffset)
            throw new IllegalArgumentException("unfit offset:"+actualOffset+"/"+offset);
        in.readBytes(data);
        //生成消息对象
        T packet= interpreter.fromByte(data);
        packet.mVersion=version;
        packet.mCommand=command;
        return packet;
    }

    private void checkLength(ByteBuf in, int expectedLength) throws IllegalArgumentException{
        int actualLength= in.readableBytes();
        if(actualLength < expectedLength)
            throw new IllegalArgumentException("invalid length of "+actualLength+"/"+expectedLength);
    }

    public void encodeInvalidPacket(ByteBuf out){
        out.writeInt(Packet.MAGIC-1);//导致服务器关闭客户端连接
    }
}
